<template>
  <div :class="['pageable-table', { 'auto-height': autoHeight }]">
    <div class="pageable-table-wrapper">
      <!--顶部工具条插槽-->
      <slot name="toolbar"></slot>
      <!--表格-->
      <ElTable
        ref="table"
        :data="records"
        :class="['flex-item', { 'header-bold': headerBold }]"
        :border="border"
        stripe
        size="small"
        v-bind="filterTableProperties(mergeAttrs)"
        @hook:updated="updateTableScrollPosition"
        @sort-change="onSortChange"
        @row-dblclick="rowDbClick"
        @row-click="rowClick"
        @cell-click="hideTooltip"
        @current-change="onCurrentChange"
        @selection-change="handleSelectionChange"
        v-on="filterTableEvents($listeners)">
        <!--多选列-->
        <TableColumn v-if="multiple && selection" key="multiple" type="selection" :selectable="selectable" :width="selectionWidth" align="center" />
        <!--单选列-->
        <TableColumn
          v-if="!multiple && selection"
          key="single"
          :sortable="false"
          type="single-selection"
          class-name="single-select-column"
          :width="selectionWidth"
          align="center">
          <template #header>
            <ElRadio :value="!!currentRow" :label="true"><span></span></ElRadio>
          </template>
          <template #default="{ row }">
            <ElRadio :label="true" :disabled="!selectable(row)" :value="currentRow && currentRow[$attrs['row-key'] || 'id'] === row[$attrs['row-key'] || 'id']">
              <span></span>
            </ElRadio>
          </template>
        </TableColumn>
        <!--序号列-->
        <TableColumn v-if="index" key="order" type="index" label="序号" :width="indexWidth" align="center">
          <template #default="{ $index }">
            {{ (page.page - 1) * page.limit + $index + 1 || $index + 1 }}
          </template>
        </TableColumn>
        <!--内容为空时的提示-->
        <template #empty>
          <!--内容为空插槽-->
          <slot name="empty">
            <template v-if="tableStatus === TABLE_STATUS.loading">
              <span class="pageable-table-loading"><i class="table-status-icon el-icon-loading"></i> 加载中...</span>
            </template>
            <template v-else-if="tableStatus === TABLE_STATUS.error">
              <ElButton class="pageable-table-error" icon="table-status-icon el-icon-error" @click="refresh">加载失败，点击重试！</ElButton>
            </template>
            <ElEmpty v-else />
          </slot>
        </template>
        <!--底部追加列插槽-->
        <template #append>
          <slot name="append"></slot>
        </template>
        <!-- <template v-if="columns && columns.length">
          <TableColumn v-for="(item, position) in headers" v-bind="item" :key="`${item.prop}-${position}`"/>
        </template>-->
        <!--<template v-else-if="tableColumns">-->
        <!--外部列-->
        <template v-if="tableColumns">
          <TableColumnFilterable
            v-for="(item, position) in tableColumns.filter(value => !value.hide)"
            :key="`${item.props?.prop || item.prop}-${position}`"
            v-bind="item" />
        </template>
        <!--未设置列提醒-->
        <template v-else>
          <TableColumn label="点击表格行复制模板">
            <template #default="{ row }">
              <pre class="pointer" @click="copy(row)">{{ JSON.stringify(row, null, '\t') }}</pre>
            </template>
          </TableColumn>
        </template>
      </ElTable>
      <!--底部分页工具条-->
      <ElPagination
        v-if="paging"
        :current-page="page.page"
        :hide-on-single-page="hideOnSinglePage"
        :page-size="page.limit"
        :total="page.total"
        :page-sizes="[10, 25, 50, 100]"
        class="flex-not-shrink"
        background
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="
          limit => {
            page.limit = limit
          }
        "
        @current-change="
          curPage => {
            page.page = curPage
          }
        " />
      <!--列设置面板-->
      <TableColumnFilter :visible.sync="showSettings" :columns="tableColumns" />
    </div>
  </div>
</template>

<script>
  import { httpGet, httpPost } from '@/http'
  import { copyData } from '@/util/clipboard'
  import { hasOwn } from '@web-utils/core'
  import { merge } from '@web-utils/integrations/lodash'
  import { filterTableColumnProperties, filterTableEvents, filterTableProperties } from '@/util'
  import { getDictLabel } from '@/util/dictionary'
  import TableColumnFilterable from '@/components/table/TableColumnFilterable.vue'
  import TableColumnFilter from '@/components/table/TableColumnFilter.vue'
  import { isEqual } from 'lodash-es'

  function isColumnsEqual(columns, others) {
    const a = columns.map(value => {
      const { hide, filterable, allowSort, sortable, fixed, ...rest } = value
      return rest
    })

    const b = others.map(value => {
      const { hide, filterable, allowSort, sortable, fixed, ...rest } = value
      return rest
    })

    return isEqual(a, b)
  }

  function getTemplate(row) {
    let tpl = ''
    for (const key in row) {
      tpl += `<TableColumn prop="${key}" label="${key}"/>\n`
    }
    return tpl
  }

  const TABLE_STATUS = {
    loading: 1,
    error: -1,
    empty: 2,
    success: 0
  }

  export default {
    name: 'PageableTable',
    components: { TableColumnFilter, TableColumnFilterable },
    emits: ['page-loaded', 'row-dblclick'],
    bus: {
      reloadTableView(viewId) {
        if (viewId === this.viewId) {
          this.getPage()
        }
      }
    },
    inheritAttrs: false,
    props: {
      selectionWidth: {
        type: String,
        default: '45'
      },
      viewId: String,
      url: String,
      httpGet: Boolean,
      hideOnSinglePage: Boolean,
      api: Function,
      model: Object,
      ascs: {
        type: Array,
        default: () => []
      },
      descs: {
        type: Array,
        default: () => []
      },
      headerBold: Boolean,
      autoHeight: Boolean,
      immediate: {
        type: Boolean,
        default: true
      },
      index: {
        type: Boolean,
        default: true
      },
      multiple: {
        type: Boolean,
        default: true
      },
      selection: {
        type: Boolean,
        default: true
      },
      border: {
        type: Boolean,
        default: true
      },
      reloadOnModelChange: {
        type: Boolean,
        default: false
      },
      columns: Array,
      pageSize: {
        type: Number,
        default: 25
      },
      dicOptions: {
        type: Array,
        default: () => []
      },
      paging: {
        type: Boolean,
        default: true
      },
      clickRowSelect: Boolean,
      payloadStyle: String,
      rowKey: {
        type: String,
        default: 'id'
      },
      disabledRowKeys: Array
    },
    data() {
      const limit = this.pageSize
      return {
        TABLE_STATUS: Object.freeze(TABLE_STATUS),
        tableStatus: TABLE_STATUS.empty,
        records: [],
        currentRow: null,
        showSettings: false,
        selectionRow: [],
        page: { total: 0, page: 1, limit, ascs: this.ascs, descs: this.descs },
        tableColumns: [],
        renderKey: 1
      }
    },
    computed: {
      guard() {
        if (this.reloadOnModelChange) {
          return {
            asc: this.page.ascs,
            desc: this.page.descs,
            page: this.page.page,
            limit: this.page.limit,
            model: this.model
          }
        } else {
          return { asc: this.page.ascs, desc: this.page.descs, page: this.page.page, limit: this.page.limit }
        }
      },
      method() {
        return this.httpGet ? httpGet : httpPost
      },
      indexWidth() {
        const indexStr = `${(this.page.page - 1) * this.page.limit + (this.records?.length ?? 0)}`
        if (indexStr.length > 3) {
          return 50 + (indexStr.length - 3) * 10
        } else {
          return 50
        }
      },
      mergeAttrs() {
        return merge({}, this.$attrs, { height: this.autoHeight ? null : 'auto' })
      },
      headers() {
        return this.columns?.map(filterTableColumnProperties) || []
      }
    },
    watch: {
      url: { handler: 'reload' },
      api: { handler: 'reload' },
      '$attrs.data': 'getPage'
    },
    created: function () {
      this.$watch(() => this.guard, this.getPage, { deep: true, immediate: this.immediate })
      this.$watch(
        () => ({ columns: this.columns, renderKey: this.renderKey }),
        newVal => {
          const tableColumns = []
          if (newVal) {
            const defaultSlots = this.$slots.default
            const { columns } = newVal
            if (defaultSlots) {
              defaultSlots.forEach(value => {
                if (!value.isComment && value.data) {
                  const { attrs, props, scopedSlots } = value.data
                  value.data.props = value.data.props || {}
                  tableColumns.push({
                    ...attrs,
                    ...props,
                    scopedSlots,
                    hide: false,
                    isVNode: true,
                    filterable: false,
                    allowSort: value.data.props.sortable !== false,
                    sortable: false,
                    fixed: false
                  })
                }
              })
              this.updateColumns(tableColumns)
            } else if (columns) {
              columns.forEach(value => {
                tableColumns.push({
                  props: value,
                  hide: false,
                  filterable: false,
                  allowSort: value.sortable !== false,
                  sortable: false,
                  fixed: false
                })
              })
              this.updateColumns(tableColumns)
            }
          }
        },
        { immediate: true }
      )
    },
    mounted() {
      this.$refs.table.bodyWrapper.addEventListener('scroll', this.onTableScroll)
    },
    beforeUpdate() {
      this.renderKey++
    },
    beforeDestroy() {
      this.$refs.table.bodyWrapper.removeEventListener('scroll', this.onTableScroll)
    },
    methods: {
      rowDbClick(...args) {
        this.hideTooltip()
        this.$emit('row-dblclick', ...args)
      },
      rowClick(row, column, event) {
        if (this.clickRowSelect) {
          if (this.selectable(row)) {
            const refsElTable = this.$refs.table // 获取表格对象
            const findRow = this.selectionRow.find(value => value[this.rowKey] === row[this.rowKey])
            if (findRow) {
              refsElTable.toggleRowSelection(row, false)
              return
            }
            refsElTable.toggleRowSelection(row) // 调用选中行方法
          }
        }
      },
      hideTooltip() {
        const body = this.$refs.table.$children.find(value => value.$options?.name === 'ElTableBody')
        body?.$refs.tooltip?.hide()
      },
      async updateTableScrollPosition() {
        if (this.$refs.table.bodyWrapper) {
          this.scrollTo(this.tableScrollTop, this.tableScrollLeft)
        }
      },
      filterTableProperties,
      filterTableEvents,
      filterTableColumnProperties,
      onTableScroll() {
        this.tableScrollTop = this.$refs.table.bodyWrapper.scrollTop
        this.tableScrollLeft = this.$refs.table.bodyWrapper.scrollLeft
      },
      copy: row => copyData(getTemplate(row)),
      onSortChange({ prop, order }) {
        if (this.payloadStyle !== 'body') {
          this.page.ascs = order === 'ascending' ? prop : ''
          this.page.descs = order === 'descending' ? prop : ''
        } else {
          this.page.ascs = order === 'ascending' ? [prop] : this.ascs
          this.page.descs = order === 'descending' ? [prop] : this.descs
        }
      },
      onCurrentChange(row) {
        if (!this.selectable(row)) {
          return
        }
        this.currentRow = row
      },
      handleSelectionChange(rows) {
        this.selectionRow = rows
      },
      clearSelection() {
        this.$refs.table.clearSelection()
      },
      getPageQuery() {
        return this.payloadStyle !== 'body'
          ? { ...this.page, current: this.page.page, size: this.page.limit, ...(this.model || {}) }
          : { page: { ...this.page, current: this.page.page, size: this.page.limit }, example: this.model || {} }
      },
      async getPage() {
        if (hasOwn(this.$attrs, 'data')) {
          this.records = this.$attrs.data
          this.page.page = 1
          this.page.total = this.records.length
          this.page.limit = this.page.total
          return
        }

        this.records = []
        let res

        const page = this.getPageQuery()

        if (this.api || this.url) {
          this.tableStatus = TABLE_STATUS.loading
          try {
            if (this.api) {
              res = await this.api(page)
            }
            if (this.url) {
              res = await this.method(this.url, page)
            }
          } catch (e) {
            this.tableStatus = TABLE_STATUS.error
            return
          }
        } else {
          this.$notify.warning({
            title: '开发提示！',
            message: 'PageableTable 请提供 url(String) 或 api(Function) 属性'
          })
          return
        }
        const {
          data: { data }
        } = res || {}
        const { total, records } = data || {}
        this.page.total = Number(total)
        this.records = await this.mapRow(records)
        if (!records || !records.length) {
          this.tableStatus = TABLE_STATUS.empty
        } else {
          this.tableStatus = TABLE_STATUS.success
        }
        // fix element-ui 2.15.7 table bug
        this.$refs.table?.doLayout()
        this.$emit('page-loaded', data)
      },
      async refresh() {
        await this.getPage()
      },
      async reload() {
        this.page = {
          ...this.$options.data.call(this).page,
          limit: this.page.limit
        }
      },
      scrollTo(top, left) {
        if (top) {
          this.$refs.table.bodyWrapper.scrollTop = top
        }
        if (left) {
          this.$refs.table.bodyWrapper.scrollLeft = left
        }
      },
      toggleRowSelection(row, selected) {
        this.$refs.table.toggleRowSelection(row, selected)
      },
      async mapRow(records) {
        return records.map(row => {
          this.dicOptions.forEach(async item => {
            row[`$${item.prop}`] = row[item.prop]
            if (item.dicData) {
              row[item.prop] = item.dicData.find(value => value.value === row[item.prop])?.label || row[item.prop]
            } else {
              row[item.prop] = await getDictLabel(item.dicType, row[item.prop])
            }
          })
          return row
        })
      },
      toggleSettings() {
        this.showSettings = !this.showSettings
      },
      selectable(row) {
        return !row?.disabled && !this.disabledRowKeys?.includes(row?.[this.rowKey])
      },
      updateColumns(tableColumns) {
        if (!isColumnsEqual(this.tableColumns, tableColumns)) {
          const rowMap = {}
          this.tableColumns.forEach(value => {
            rowMap[value.prop] = value
          })
          tableColumns.map(value => {
            const column = rowMap[value.prop]
            if (column) {
              const { hide, filterable, allowSort, sortable, fixed } = column
              merge(column, value)
              column.hide = hide
              column.filterable = filterable
              column.allowSort = allowSort
              column.sortable = sortable
              column.fixed = fixed
              return column
            }
            return value
          })

          this.tableColumns = tableColumns
        }
      }
    }
  }
</script>

<style lang="scss">
  .pageable-table {
    @include flex-item-relative;
    height: 100%;

    .single-select-column {
      .el-radio__label {
        padding: 0;
      }
    }

    &.auto-height {
      position: static;
      height: auto;

      .el-table {
        position: relative;
        min-height: 200px;
      }

      .pageable-table-wrapper {
        position: static;
      }
    }

    .pageable-table-wrapper {
      @include flex-column;
      @include absolute-box;
    }

    .el-pagination {
      background-color: #fff;
    }

    .table-status-icon {
      font-size: 20px;
    }

    .pageable-table-loading {
      display: inline-flex;
      align-items: center;
    }

    .pageable-table-error {
      display: inline-flex;
      align-items: center;
      color: #f00;
      border: 1px solid #fca4a4;

      &:hover {
        background-color: #fff0f0;
      }
    }

    .pageable-table-total {
      padding: 0 10px 10px;
    }

    .underline {
      position: relative;

      &::after {
        position: absolute;
        bottom: 0;
        left: 0;
        z-index: 1;
        width: 100%;
        height: 1px;
        background-color: #606266;
        content: '';
      }
    }

    .el-table th {
      color: #000000d9;
      word-break: break-word;
      background-color: #fafafa;
    }

    .total {
      padding: 10px;
    }
  }
</style>